import { Injectable } from '@nestjs/common';
import Decimal from 'decimal.js';
import { InjectRepository } from '@nestjs/typeorm';
import { In, LessThan, MoreThan, MoreThanOrEqual, Repository } from 'typeorm';
import { doubleEntryMap, fundingTableType, StageFunding, StageFundingMinusType, StageFundingPlusType, fundingSort } from '../entity/stageFunding.entity';
import { StageFundingFlow } from '../entity/stageFundingFlow.entity';
import { StageInterest } from '../entity/stageInterest.entity';
import { CreateDoubleEntryStageFundingDto, CreateStageFundingBatchDto, CreateStageFundingDto, ListStageFundingBorrowerStatementDto } from '../dto/stageFunding.dto';
import { UpdateStageFundingDto } from '../dto/stageFunding.dto';
import { ListStageFundingSettlementDto } from '../dto/stageFunding.dto';
import { Stage, InterstMethod, StageStatus } from '../entity/stage.entity';
import { PaginatedResult } from '../../libs/dto/page.dto';
import { StageService } from './stage.service';
import { AppException } from '@libs/exception';
import { InterestFeeService } from './interest.service';
import { StageFundingDeduct } from '../entity/stageFundingDeduct.entity';
import { Project } from '../entity/project.entity';
import { isLeapYear } from '../entity/utils';
import { Account } from '@/account/entity/account.entity';

@Injectable()
export class StageFundingService {
  constructor(
    @InjectRepository(StageFunding)
    private readonly stageFundingRepo: Repository<StageFunding>,
    @InjectRepository(StageInterest)
    private readonly stageInterestRepo: Repository<StageInterest>,
    @InjectRepository(StageFundingFlow)
    private readonly flowRepo: Repository<StageFundingFlow>,
    @InjectRepository(Account)
    private readonly accountRepo: Repository<Account>,
    @InjectRepository(StageFundingDeduct)
    private readonly stageFundingDeductRepo: Repository<StageFundingDeduct>,
    @InjectRepository(Stage)
    private readonly stageRepo: Repository<Stage>,
    private interestService: InterestFeeService,
  ) {}

  async listSettlement(dto: ListStageFundingSettlementDto): Promise<StageFunding[]> {
    const query = this.stageFundingRepo
      .createQueryBuilder('funding')
      .leftJoinAndSelect('funding.flows', 'flows')
      .where({ stageId: dto.stageId, isSettlement: true })
      .orWhere({type: StageFundingMinusType.LoanDrawdown})
      .orderBy('funding.id', 'ASC');

    const results = await query.getMany();
    return results;
  }

  async listBorrowerStatement(dto: ListStageFundingBorrowerStatementDto): Promise<StageFunding[]> {
    const query = this.stageFundingRepo
      .createQueryBuilder('funding')
      .where({ stageId: dto.stageId })
      .orderBy('end_date', 'ASC')
      .orderBy('start_date', 'ASC')
      .addOrderBy(`
        CASE funding.type
          ${fundingSort.map((type, index) => `WHEN '${type}' THEN ${index}`).join(' ')}
          ELSE ${fundingSort.length}
        END
      `, 'ASC');

    const results = await query.getMany();
    for(const funding of results) {
      if(funding.doubleEntryId) {
        const doubleEntry = await this.stageFundingRepo.findOneBy({id: funding.doubleEntryId})
        if (doubleEntry?.deductId) {
          const deduct = await this.stageFundingDeductRepo.findOneBy({toDeductId: doubleEntry.id});
          funding.value = funding.value.add(deduct.amount);
        }
      }
    }
    return results;
  }

  async settlement(dto: CreateStageFundingBatchDto) {
    const stage = await this.stageRepo.findOne({ where: { id: dto.stageId }, relations: ['project'] });
    if (!stage) {
      throw new AppException('Stage not found', { httpCode: 404 });
    }

    stage.status = dto.type;
    const rate = stage.status == StageStatus.DEFAULT ? stage.defaultRate : stage.interestRate;

    for (const settlement of dto.data) {
      await this.create(settlement);
    }
    // 创建结算信息
    for (const settlement of dto.data) {
      if (!settlement.deductSns) {
      	continue;
      }
      for (const d of settlement?.deductSns) {
        const fromDeduct = await this.stageFundingRepo.findOneBy({sn: settlement.sn});
        const toDeduct = await this.stageFundingRepo.findOne({where: {sn: d.sn}});
        toDeduct.deductId = fromDeduct.id
        await this.stageFundingRepo.save(toDeduct);
        await this.stageFundingDeductRepo.save({
          fromDeductId: fromDeduct.id,
          toDeductId: toDeduct.id,
          amount: d.amount
        })
      }
    }

    // 创建借款信息
    // await this.create({
    //   stageId: stage.id,
    //   name: StageFundingMinusType.LoanDrawdown,
    //   type: StageFundingMinusType.LoanDrawdown,
    //   startDate: stage.loanStartDate,
    //   endDate: stage.loanStartDate,
    //   value: stage.loanAmount,
    //   isDoubleEntry: false,
    // });
    await this.create({
      stageId: stage.id,
      name: StageFundingPlusType.LoanRepayment,
      type: StageFundingPlusType.LoanRepayment,
      value: stage.loanAmount,
      startDate: stage.loanRepaymentDate,
      endDate: stage.loanRepaymentDate,
      isDoubleEntry: false,
    });

    // 更新每日利息，费用等
    await this.updateInterestAndFee(stage,stage.project, rate);
  
    const prepaidFeeEntity = await this.stageFundingRepo.findOneBy({stageId: stage.id, type: StageFundingPlusType.InterestReservePayment});
    let prepaidMonths = await this.calPrepaidMonths(stage, rate, prepaidFeeEntity?.value);
    if (prepaidFeeEntity) {
      prepaidFeeEntity.endDate = new Date(prepaidFeeEntity.startDate)
      prepaidFeeEntity.endDate.setMonth(prepaidFeeEntity.startDate.getMonth() + prepaidMonths.toNumber())
      prepaidFeeEntity.endDate.setDate(prepaidFeeEntity.startDate.getDate() -1 )
      if (isLeapYear(prepaidFeeEntity.endDate) && prepaidFeeEntity.endDate.getMonth() ==1 && prepaidFeeEntity.endDate.getDate() > 29) {
        prepaidFeeEntity.endDate.setDate(29)
      }
      if (prepaidFeeEntity.endDate.getMonth() ==1 && prepaidFeeEntity.endDate.getDate() > 28) {
        prepaidFeeEntity.endDate.setDate(28 )
      }
      await this.stageFundingRepo.save(prepaidFeeEntity);
    }
    await this.updateMonthlyInterest(stage, prepaidMonths);

    // Update stage status
    await this.stageRepo.update(stage.id, { status: dto.type });
    return stage;
  }

  async calPrepaidMonths(stage: Stage,rate: Decimal, prepaidFee: Decimal) {
    let prepaidMonths = new Decimal(0);
    stage.fundings = [];
    // 更新资金信息

    if (prepaidFee) {
      // Calculate prepaid months (floor)
      if(stage.interestMethod === InterstMethod.MONTHLY) {
        const monthlyInterest = stage.loanAmount.mul(rate).div(12);
        prepaidMonths = new Decimal(prepaidFee).div(monthlyInterest).round();
      } else if(stage.interestMethod === InterstMethod.DAILY){
        const prepaidDays = new Decimal(prepaidFee).div(stage.loanAmount.mul(rate).div(365)).floor();
        const prepaidEndDate = new Date(stage.loanStartDate);
        prepaidEndDate.setDate(prepaidEndDate.getDate() + prepaidDays.toNumber());
        const startDay = stage.loanStartDate.getDate();
        const endDay = prepaidEndDate.getDate();
        const monthDiff = (prepaidEndDate.getFullYear() - stage.loanStartDate.getFullYear()) * 12 + 
          (prepaidEndDate.getMonth() - stage.loanStartDate.getMonth());
        prepaidMonths = new Decimal(endDay >= startDay ? monthDiff : monthDiff - 1).round();
      } else {
        throw new AppException('Invalid interest method', { httpCode: 400 });
      }
    }
    return prepaidMonths;
  }

  async updateInterestAndFee(stage: Stage, project: Project, rate: Decimal) {
    // 更新每日利息，费用等
    if (stage.interestMethod && stage.loanStartDate && stage.loanRepaymentDate) {
      await this.interestService.updateDailyStageInterest(stage, rate);
    }
    await this.interestService.updateMonthlyLineFee(project, stage);
  }

  /**
   * 更新stage的月供
   * @param stage 
   * @param prepaidMonths 
   * @param newRepaymentDate 为空代表全两更新，否则只从newRepaymentDate开始更新
   *@returns 
   */
  async updateMonthlyInterest(stage: Stage, prepaidMonths: Decimal, newRepaymentDate?: Date) {
    if (newRepaymentDate) {
      await this.stageFundingRepo
        .createQueryBuilder()
        .delete()
        .where('endDate >= :repaymentDate', { repaymentDate: newRepaymentDate})
        .andWhere('stageId = :stageId', { stageId: stage.id })
        .andWhere('type IN (:...types)', {
          types: [StageFundingMinusType.InterestCharge, StageFundingPlusType.InterestChargePayment]
        })
        .execute();
    } else {
      await this.stageFundingRepo.delete({ stageId: stage.id, type: In([StageFundingMinusType.InterestCharge, StageFundingPlusType.InterestChargePayment ]) });
    }
    // Add monthly interest payments if prepaid
    let startDate = new Date(stage.loanStartDate);
    const startDay = startDate.getDate();
    const endDay = startDay - 1;
    let chargeMonth = 0;
    while (startDate < stage.loanRepaymentDate) {
      // Calculate endDate
      let nextMonthDate = new Date(startDate);
      
      const daysInMonth = new Date(startDate.getFullYear(), startDate.getMonth() + 2, 0).getDate();
      if (daysInMonth < endDay) {
        nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
        nextMonthDate.setDate(0);
      } else {
        const currentStartDay = startDate.getDate();
        if (endDay == 0) {
          nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
          nextMonthDate.setDate(endDay);
        } else if (currentStartDay >= endDay) {
          nextMonthDate.setDate(endDay);
          nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
        } else {
          nextMonthDate.setDate(endDay);
        }
      }
      // Handle cases where next month doesn't have the same day (e.g. 2025-01-31 -> 2025-02-28)
      // if (nextMonthDate.getMonth() !== (startDate.getMonth() + 1) % 12) {
      //   nextMonthDate.setDate(0); // Set to last day of previous month
      // } else {
      //   nextMonthDate.setDate(nextMonthDate.getDate() - 1);
      // }
      const isLastPayment = nextMonthDate > stage.loanRepaymentDate;
      const endDate = isLastPayment ? stage.loanRepaymentDate : nextMonthDate;

      const aggr = await this.stageInterestRepo
        .createQueryBuilder('interest')
        .select('SUM(interest.money)', 'totalInterest')
        .where('interest.stageId = :stageId', { stageId: stage.id })
        .andWhere('interest.date BETWEEN :start AND :end', {
          start: startDate,
          end: endDate,
        })
        .getRawOne();
      const totalInterest = new Decimal(aggr?.totalInterest || 0);

      if (!newRepaymentDate || newRepaymentDate.getTime() <= endDate.getTime()) {
        // Add InterestCharge
        stage.fundings.push(
          this.stageFundingRepo.create({
            stageId: stage.id,
            name: StageFundingMinusType.InterestCharge,
            type: StageFundingMinusType.InterestCharge,
            value: totalInterest,
            startDate: new Date(startDate),
            endDate: endDate,
            isDoubleEntry: false,
            isSettlement: false,
          }),
        );
  
        // Add interestPayment
        if (prepaidMonths.lte(chargeMonth)) {
          stage.fundings.push(
            this.stageFundingRepo.create({
              stageId: stage.id,
              name: StageFundingPlusType.InterestChargePayment,
              type: StageFundingPlusType.InterestChargePayment,
              value: totalInterest,
              startDate: new Date(startDate),
              endDate: endDate,
              isDoubleEntry: true,
              isSettlement: false,
            }),
          );
        }
      }

      startDate = new Date(nextMonthDate);
      startDate.setDate(startDate.getDate() + 1);
      chargeMonth++;
    }
    if (stage.fundings && stage.fundings.length > 0) {
      await this.stageFundingRepo.save(stage.fundings);
    }
  }

  async createMany(dto: CreateStageFundingDto[]){
    const res = []
    for(const d of dto) {
      res.push(await this.create(d))
    }
    return res;
  }

  async create(dto: CreateStageFundingDto) {
    const stage = await this.stageRepo.findOne({ where: {id: dto.stageId}, relations: ['project'] });
    if (!stage) {
      throw new AppException('Stage not found', { httpCode: 404 });
    }

    const deduct = dto.deductId ? await this.stageFundingRepo.findOneBy({ id: dto.deductId }) : null;


    const funding = this.stageFundingRepo.create({
      sn: dto.sn,
      name: dto.name,
      stageId: stage.id,
      type: dto.type,
      value: dto.value,
      transcationDate: dto.transcationDate,
      startDate: dto.startDate || stage.loanStartDate,
      endDate: dto.endDate || stage.loanStartDate, // 默认开始结束都是项目借款开始时间
      bankAccountName: dto.bankAccountName,
      bankBSBNumber: dto.bankBSBNumber,
      bankAccountNum: dto.bankAccountNum,
      isDoubleEntry: false,
      deductId: deduct ? deduct.id : null,
      isSettlement: true,
    });

    if (dto.type === StageFundingPlusType.InterestReservePayment) {
      const rate = stage.status == StageStatus.DEFAULT ? stage.defaultRate : stage.interestRate;
      let prepaidMonths = await this.calPrepaidMonths(stage, rate, funding?.value);
      funding.startDate = new Date(funding.startDate)
      funding.endDate = new Date(funding.startDate)
      funding.endDate.setMonth(funding.endDate.getMonth() + prepaidMonths.toNumber())
      funding.endDate.setDate(funding.startDate.getDate() -1 )
      if (isLeapYear(funding.endDate) && funding.endDate.getMonth() ==1 && funding.endDate.getDate() > 29) {
        funding.endDate.setDate(29)
      }
      if (funding.endDate.getMonth() ==1 && funding.endDate.getDate() > 28) {
        funding.endDate.setDate(28 )
      }
    }

    // Save the funding first to get its ID
    const savedFunding = await this.stageFundingRepo.save(funding);

    if (dto.flows && dto.flows.length > 0) {
      // Create flows with the saved funding's ID
      const flows = dto.flows.map((flowDto) =>
        this.flowRepo.create({
          ...flowDto,
          stageFunding: savedFunding,
        }),
      );

      // Save the flows
      savedFunding.flows = await this.flowRepo.save(flows);
    }

    const goodlandLtd = await this.accountRepo.findOneByOrFail({ name: 'Goodland Management Pty Ltd' });
    if (dto.type === StageFundingPlusType.FeeRefundPayment) {
      const flow = this.flowRepo.create({
          accountId: goodlandLtd.id,
          stageFundingId: savedFunding.id,
          amount: dto.value,
          bankDetail: '',
          note: ''
        })
      const saveFlow = await this.flowRepo.save(flow);
      savedFunding.flows = savedFunding.flows ?  [...savedFunding.flows, saveFlow] : [saveFlow]
    } else if (dto.type === StageFundingMinusType.InterestRefund){
      const flow = this.flowRepo.create({
          accountId: stage.project.borrowerId,
          stageFundingId: savedFunding.id,
          amount: dto.value,
          bankDetail: '',
          note: ''
        })
      const saveFlow = await this.flowRepo.save(flow);
      savedFunding.flows = savedFunding.flows ?  [...savedFunding.flows, saveFlow] : [saveFlow]
    }

    if (dto.isDoubleEntry && doubleEntryMap[dto.type] && dto.number ) {
      for(let i = 0; i < dto.number; i++) {
        let startDate = new Date(funding.startDate)
        startDate.setMonth(startDate.getMonth() + i);
        let endDate = new Date(funding.endDate)
        endDate.setMonth(endDate.getMonth() + i);

        const stageFunding = await this.stageFundingRepo.save({
          name: doubleEntryMap[dto.type],
          stageId: stage.id,
          type: doubleEntryMap[dto.type],
          value: dto.value,
          startDate: startDate,
          endDate: endDate, // 默认开始结束都是项目借款开始时间
          transcationDate: dto.transcationDate,
          isDoubleEntry: true,
          deductId: null,
          doubleEntryId: savedFunding.id,
          isSettlement: true,
        })

        // FeeRefundRe
        if (doubleEntryMap[dto.type] === StageFundingMinusType.FeeRefund) {
          const flow = this.flowRepo.create({
            accountId: stage.project.borrowerId,
            stageFundingId: savedFunding.id,
            amount: dto.value,
            bankDetail: '',
            note: ''
          })
          const saveFlow = await this.flowRepo.save(flow);
          stageFunding.flows = stageFunding.flows ?  [...stageFunding.flows, saveFlow] : [saveFlow]
        }
      }
      
    }

    // 创建结算信息
    if (dto?.deductSns) {
      for (const d of dto?.deductSns) {
        const fromDeduct = await this.stageFundingRepo.findOneBy({sn: dto.sn});
        const toDeduct = await this.stageFundingRepo.findOne({where: {sn: d.sn}});
        toDeduct.deductId = fromDeduct.id
        await this.stageFundingRepo.save(toDeduct);
        await this.stageFundingDeductRepo.save({
          fromDeductId: fromDeduct.id,
          toDeductId: toDeduct.id,
          amount: d.amount
        })
      }
    }

    return savedFunding;
  }

  async updateLoanRepaymentDate(stageId: number, date: Date) {
    const stageFunding = await this.stageFundingRepo.findOneByOrFail({ stageId: stageId, type: StageFundingPlusType.LoanRepayment });
    stageFunding.endDate = date;
    stageFunding.startDate = date;
    return this.stageFundingRepo.save(stageFunding);
  }

  async update(id: number, dto: UpdateStageFundingDto) {
    const funding = await this.stageFundingRepo.findOne({
      where: { id },
    });

    if (!funding) {
      throw new AppException('Stage funding not found', { httpCode: 404 });
    }

    if (dto.stageId) {
      const stage = await this.stageRepo.findOneBy({ id: dto.stageId });
      if (!stage) {
        throw new AppException('Stage not found', { httpCode: 404 });
      }
      funding.stage = stage;
    }

    if (dto.type) {
      funding.type = dto.type;
    }
    if (dto.name) {
      funding.name = dto.name;
    }
    if (dto.value) funding.value = new Decimal(dto.value);
    if (dto.transcationDate) funding.transcationDate = dto.transcationDate;
    if (dto.startDate) funding.startDate = dto.startDate;
    if (dto.endDate) funding.endDate = dto.endDate;
    if (dto.bankAccountName) funding.bankAccountName = dto.bankAccountName;
    if (dto.bankBSBNumber) funding.bankBSBNumber = dto.bankBSBNumber;
    if (dto.bankAccountNum) funding.bankAccountNum = dto.bankAccountNum;
    if (dto.isDoubleEntry === true || dto.isDoubleEntry == false) funding.isDoubleEntry = dto.isDoubleEntry;
    // if (dto.number || dto.number == 0) funding.number = dto.number;

    if (dto.deductId) {
      const deduct = await this.stageFundingRepo.findOneBy({ id: dto.deductId });
      if (!deduct) {
        throw new AppException('Deduct funding not found', { httpCode: 404 });
      }
      funding.deduct = deduct;
    }

    if (dto.flows && dto.flows.length > 0) {
      // Remove existing flows
      await this.flowRepo.delete({ stageFunding: { id } });

      // Create new flows
      await Promise.all(
        dto.flows.map((flowDto) => {
          this.flowRepo.save({
            ...flowDto,
            stageFundingId: funding.id,
          });
        }),
      );
    }

    if (dto.updateDoubleEntry ) {
      if (!doubleEntryMap[funding.type]) {
        await this.flowRepo.delete({ stageFundingId: funding.id });
        await this.stageFundingRepo.delete({ doubleEntryId: funding.id });
      } else {
        const doubleEntryFundings = await this.stageFundingRepo.findBy({ doubleEntryId: funding.id });
        if (doubleEntryFundings.length > 0) {
          for (const doubleEntryFunding of doubleEntryFundings) {
            doubleEntryFunding.type = doubleEntryMap[funding.type];
            doubleEntryFunding.name = doubleEntryMap[funding.type];
            doubleEntryFunding.value = funding.value;
            doubleEntryFunding.startDate = funding.startDate;
            doubleEntryFunding.endDate = funding.endDate;
            doubleEntryFunding.transcationDate = funding.transcationDate;
            await this.stageFundingRepo.save(doubleEntryFunding);
          }
        } else {
          await this.stageFundingRepo.save({
            name: doubleEntryMap[funding.type],
            stageId: funding.stageId,
            type: doubleEntryMap[funding.type],
            value: funding.value,
            startDate: funding.startDate,
            endDate: funding.endDate, // 默认开始结束都是项目借款开始时间
            transcationDate: funding.transcationDate,
            isDoubleEntry: true,
            deductId: null,
            doubleEntryId: funding.id,
            isSettlement: true,
          })
        }
      }
    }

    return this.stageFundingRepo.save(funding);
  }

  async delete(id: number, deleteDoubleEntry: boolean=false) {
    const funding = await this.stageFundingRepo.findOneBy({ id });
    if (deleteDoubleEntry) {
      if (funding.doubleEntryId) {
        await this.flowRepo.delete({ stageFundingId: funding.doubleEntryId });
        await this.stageFundingRepo.delete({ id: funding.doubleEntryId });
      } 
      await this.stageFundingRepo.delete({doubleEntryId: id});
    }
    await this.flowRepo.delete({ stageFundingId: id  });
    return this.stageFundingRepo.delete(id);

  }

  async findById(id: number) {
    return this.stageFundingRepo.findOne({
      where: { id },
      relations: ['flows'],
    });
  }

  async createDoubleEntry(id: number, dto: CreateDoubleEntryStageFundingDto) {
    const funding = await this.stageFundingRepo.findOneByOrFail({id});
    const stage = await this.stageRepo.findOne({
      where: {
        id: funding.stageId
      }
    });
    if (dto.isDoubleEntry && doubleEntryMap[funding.type] && dto.number ) {
      for(let i = 0; i < dto.number; i++) {
        let startDate = new Date(funding.startDate)
        startDate.setMonth(startDate.getMonth() + i);
        let endDate = new Date(funding.endDate)
        endDate.setMonth(endDate.getMonth() + i);

        await this.stageFundingRepo.save({
          name: doubleEntryMap[funding.type],
          stageId: stage.id,
          type: doubleEntryMap[funding.type],
          value: funding.value,
          startDate: startDate,
          endDate: endDate, // 默认开始结束都是项目借款开始时间
          transcationDate: funding.transcationDate,
          isDoubleEntry: true,
          deductId: null,
          isSettlement: true,
          doubleEntryId: funding.id,
        })
      }
    }

  }
}
